﻿using System;
using System.Collections.Generic;
using Models;
using System.Xml;
using System.Data;
using System.Xml.Linq;
using System.Diagnostics;

namespace XmlDataHandler
{
    public class ImportXML
    {
        #region Fields

        XmlImportDataAccess dataAccess;

        #endregion

        #region Creation

        // skal modtage XmlImportDataAccess objekt for at kunne lave databaseforbindelser.
        public ImportXML(XmlImportDataAccess dataAccess)
        {
            this.dataAccess = dataAccess;
        }

        #endregion

        #region Public Methods

        // entry method til importering af XML data, tjekker filens struktur 
        // og sender videre til en passende metode
        public void ImportToDatabase(string pathToFile)
        {
            // deklarer variabel til at holde XML dokumentets root element
            string rootElement = String.Empty;
            
            // bruger xml reader til at finde root navnet
            using (var reader = XmlReader.Create(pathToFile)) 
            {
                while (reader.Read()) 
                {
                    // hvis node type ikke er et xml element, continue
                    if (reader.NodeType != XmlNodeType.Element) continue;

                    // sæt root element
                    rootElement = reader.Name;  
                    break;
                }
            }

            // check root tag i input xml document, alt efter input, kald passende metode
            switch (rootElement)
            {
                case "Brands":
                    InsertBrands(pathToFile);
                    break;
                case "Categories":
                    InsertCategories(pathToFile);
                    break;
                case "Sizes":
                    InsertSizes(pathToFile);
                    break;
                case "Colors":
                    InsertColors(pathToFile);
                    break;
                case "GenericItems":
                    ImportGenericItems(pathToFile);
                    break;
                case "Items":
                    ImportItems(pathToFile);
                    break;

                default: throw new Exception("xml file does not have a matching structure");
            }
        }

        #endregion

        #region Indsættelse af Brands, Categories, Sizes og Colors

        // Logikken bag disse 4 metoder er alle ens, de itererer igennem XmlDocument, 
        // og laver multiple inserts på vores XmlImportDataAccess object

        void InsertBrands(string path)
        {
            // åbner forbindelse på vores connection object
            dataAccess.OpenConnection();

            // laver et stream af xml fil
            var reader = new XmlTextReader(path);

            // loader stream til vores XmlDocument instans
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);

            foreach (XmlNode node in doc.SelectSingleNode("//Brands").ChildNodes)
            {
                // Int32 er explicit pga. kompabilitetsproblemer når man læser csv/xml fra 64bit maskine.
                Int32 id = Convert.ToInt32(node.SelectSingleNode("Id").InnerText);
                string name = node.SelectSingleNode("Name").InnerText;

                dataAccess.InsertBrand(id, name);
            }

            // luk forbindelse
            dataAccess.CloseConnection();
        }

        void InsertCategories(string path)
        {
            dataAccess.OpenConnection();

            // laver et stream af xml fil
            var reader = new XmlTextReader(path);

            // loader stream til vores XmlDocument instans
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);

            foreach(XmlNode node in doc.SelectSingleNode("//Categories").ChildNodes)
            {
                Int32 id = Convert.ToInt32(node.SelectSingleNode("Id").InnerText);
                string name = node.SelectSingleNode("Name").InnerText;

                dataAccess.InsertCategory(id, name);
            }

            dataAccess.CloseConnection();
        }

        void InsertSizes(string path)
        {
            dataAccess.OpenConnection();

            // laver et stream af xml fil
            var reader = new XmlTextReader(path);

            // loader stream til vores XmlDocument instans
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);

            foreach (XmlNode node in doc.SelectSingleNode("//Sizes").ChildNodes)
            {
                Int32 id = Convert.ToInt32(node.SelectSingleNode("Id").InnerText);
                string name = node.SelectSingleNode("Name").InnerText;

                dataAccess.InsertSize(id, name);
            }

            dataAccess.CloseConnection();
        }

        void InsertColors(string path)
        {
            dataAccess.OpenConnection();

            // laver et stream af xml fil
            var reader = new XmlTextReader(path);

            // loader stream til vores XmlDocument instans
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);

            foreach (XmlNode node in doc.SelectSingleNode("//Colors").ChildNodes)
            {
                Int32 id = Convert.ToInt32(node.SelectSingleNode("Id").InnerText);
                string name = node.SelectSingleNode("Name").InnerText;

                dataAccess.InsertColor(id, name);
            }

            dataAccess.CloseConnection();
        }

        #endregion

        #region Import GenericItems

        void ImportGenericItems(string path)
        {
            try
            {
                // åbner den forbindelse der skal bruges igennem følgende metoder
                dataAccess.OpenConnection();

                // indsæt de items der ikke findes i databasen
                BulkInsertGenericItems(path);

                // find de elementer i databasen, der ikke eksisterer i XML dokumentet, og marker dem som inaktive
                MarkAsInactive(path);

                // find de elementer i XML dokumentet der har ændret værdi, og opdater følgende i databasen
                ModifyGenericItems(path);

                // lukker forbindelsen
                dataAccess.CloseConnection();
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        void ModifyGenericItems(string path)
        {
            // load dictionary med GenericItems fra databasen, med Id som key, og DateTime som value
            Dictionary<int, DateTime> databaseDictionary = dataAccess.GetDateTimeFromGenericItems();

            // lav et xml filestream
            using (XmlReader reader = XmlReader.Create(path))
            {
                reader.MoveToContent();

                // for hvert element i vores XML stream
                while (reader.Read()) 
                {
                    // hvis det er et XML element
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        // hvis XML elementet har det navn vi leder efter
                        if (reader.Name == "GenericItem")
                        {
                            // generer et XElement objekt vha. LINQ extensions
                            XElement el = XNode.ReadFrom(reader) as XElement;
                            if (el == null) continue;

                            // Id på XML element
                            int currentId = Convert.ToInt32(el.Element("Id").Value);

                            // DateTime for sidste ændring på XML element
                            DateTime currentDateTime = DateTime.Parse(el.Element("ModifiedDate").Value);

                            DateTime dateTime;
                            // find entry i dictionary fra databasen, hvor Id matcher med Id fra nuværende XML element
                            databaseDictionary.TryGetValue(currentId, out dateTime);

                            // sammenlign ændringsdatoer, hvis der er en ændring, skal vi opdatere i databasen
                            if (DateTime.Compare(currentDateTime, dateTime) > 0)
                            {
                                // navn på XML element
                                string name = el.Element("Name").Value;

                                // pris på XML element
                                decimal price = Decimal.Parse(el.Element("Price").Value);

                                // opdater item i databasen
                                dataAccess.UpdateGenericItem(currentId, name, price, currentDateTime);
                            }
                        }
                    }
                }
            }
        }

        void MarkAsInactive(string path)
        {
            // generer liste med Id's fra alle GenericItems i databasen
            List<int> allIds = dataAccess.AllGenericItemIds();

            using (var reader = XmlReader.Create(path))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == "GenericItem")
                        {
                            XElement el = XNode.ReadFrom(reader) as XElement;
                            if (el == null) continue;
                            int currentId = Convert.ToInt32(el.Element("Id").Value);

                            allIds.Remove(currentId);
                        }
                    }
                }
            }

            // kalder database access metode til at opdatere de items der skal være inaktive
            foreach (int e in allIds)
                dataAccess.MarkGenericItemAsInactive(e);
        }

        void BulkInsertGenericItems(string path)
        {
            // alle id'er på generic items, sorterer efter ID, 
            // så de kan bruges til binær søgning
            var all = dataAccess.AllGenericItemIds();

            // generer DataTable objekter, til at bygge tables

            var itemDescriptionTable = new DataTable();
            var genericItemsTable = new DataTable();

            // tilføjer DataColumns til vores DataTables, 
            // de skal matche en tabel og tilhørende kolonner i databasen

            #region Add DataColumns to genericItemDesceriptionTable

            var dc = new DataColumn("GenericItemId", Type.GetType("System.Int32"));
            itemDescriptionTable.Columns.Add(dc);

            dc = new DataColumn("ItemDescription", Type.GetType("System.String"));
            dc.DefaultValue = null;
            itemDescriptionTable.Columns.Add(dc);
            

            dc = new DataColumn("Material", Type.GetType("System.String"));
            dc.DefaultValue = null;
            itemDescriptionTable.Columns.Add(dc);

            dc = new DataColumn("DiscountPrice", Type.GetType("System.Decimal"));
            dc.DefaultValue = null;
            itemDescriptionTable.Columns.Add(dc);

            dc = new DataColumn("DisplayName", Type.GetType("System.String"));
            itemDescriptionTable.Columns.Add(dc);

            #endregion

            #region Add DataColumns to genericItemsTable

            dc = new DataColumn("Id", Type.GetType("System.Int32"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("Name", Type.GetType("System.String"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("BrandId", Type.GetType("System.Int32"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("CategoryId", Type.GetType("System.Int32"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("Price", Type.GetType("System.Decimal"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("Modified", Type.GetType("System.DateTime"));
            genericItemsTable.Columns.Add(dc);

            dc = new DataColumn("Available", Type.GetType("System.Int32"));
            dc.DefaultValue = 1;
            genericItemsTable.Columns.Add(dc);

            #endregion

            DataRow genericItem;
            DataRow itemDescription;

            // lav xml reader objekt
            using (var reader = XmlReader.Create(path))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    // hvis noden er et xml element
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        // hvis elementet har det rigtige navn
                        if (reader.Name == "GenericItem")
                        {
                            // generer et XElement vha. LINQ
                            var el = XNode.ReadFrom(reader) as XElement;

                            // hvis elementet ikke kunne typecastes, spring over iteration
                            if (el == null) continue;   
                            
                            // Tjek at de elementer vi skal bruge ikke er null
                            if (el.Element("Id") != null && 
                                el.Element("Name") != null && 
                                el.Element("Price") != null &&
                                el.Element("BrandId") != null &&
                                el.Element("CategoryId") != null &&
                                el.Element("ModifiedDate") != null)
                            {
                                int id = Convert.ToInt32(el.Element("Id").Value);

                                // binær søgning på id'er fra databasen, 
                                // hvis id'et IKKE findes i databasen
                                if (all.BinarySearch(id) < 0)
                                {
                                    // tilføjer XML data til vores data tables

                                    string name = el.Element("Name").Value;
                                    decimal price = Convert.ToDecimal(el.Element("Price").Value);

                                    genericItem = genericItemsTable.NewRow();
                                    genericItem["Id"] = id;
                                    genericItem["Name"] = name;
                                    genericItem["BrandId"] = Convert.ToInt32(el.Element("BrandId").Value);
                                    genericItem["CategoryId"] = Convert.ToInt32(el.Element("CategoryId").Value);
                                    genericItem["Price"] = price;
                                    genericItem["Modified"] = DateTime.Parse(el.Element("ModifiedDate").Value);
                                    genericItemsTable.Rows.Add(genericItem);


                                    itemDescription = itemDescriptionTable.NewRow();
                                    itemDescription["GenericItemId"] = id;
                                    itemDescription["DisplayName"] = name;
                                    itemDescription["DiscountPrice"] = price;
                                    itemDescriptionTable.Rows.Add(itemDescription);
                                }
                            }
                        }
                    }
                }
            }

            // insert data tables i databasen
            dataAccess.BulkInsertGenericItems(genericItemsTable);
            dataAccess.BulkInsertGenericItemDescriptions(itemDescriptionTable);
        }

        #endregion

        #region Import Items

        void ImportItems(string path)
        {
            try
            {
                dataAccess.OpenConnection();
                dataAccess.BeginTransaction();

                Stopwatch clock = Stopwatch.StartNew();

                // indsæt de items der ikke findes i databasen
                BulkInsertItems(path);

                // marker de items der ikke længere eksisterer i xml som inaktiv
                MarkItemAsInactive(path);

                // opdater de items der er blevet ændret
                ModifyItems(path);

                Console.WriteLine("Time Elapsed {0} ms", clock.ElapsedMilliseconds);

                // der var ingen fejl, commit transaction
                dataAccess.CommitTransaction();
            }
            catch (Exception e)
            {
                // der var fejl, rollback transaction
                dataAccess.RollBackTransaction();
                Console.WriteLine(e.Message);
            }
        }

        /// <summary>
        /// Her markerer vi alle elementer der ikke findes i XML udtræk som inaktiv i databasen
        /// </summary>
        void MarkItemAsInactive(string path)
        {
            // id'er på alle items i databasen, sorteret ASC - til binær søgning
            var allIds = dataAccess.AllItemIds();

            using (var reader = XmlReader.Create(path))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == "Item")
                        {
                            // generer XElement objekt for nuværende node, vha. LINQ
                            var el = XNode.ReadFrom(reader) as XElement;
                            if (el == null) continue;
                            int currentId = Convert.ToInt32(el.Element("Id").Value);

                            // led efter XElementet's ID i vores collection fra databasen
                            int index = allIds.BinarySearch(currentId);

                            // hvis elementet findes i collection
                            if (index > 0)
                            {
                                // sæt id til 0, da det ikke skal markeres som inaktivt
                                allIds[index] = 0;
                            }
                        }
                    }
                }
            }

            // kalder database access metode til at opdatere de items der skal være inaktive
            foreach (int e in allIds)
            {
                if (e == 0) continue;
                dataAccess.MarkItemAsInactive(e);
            }
        }


        /// <summary>
        /// Her indsætter vi alle items fra XML fil til databasen.
        /// Vi bruger DataTable og binær søgning frem for ekstremt mange SQL queries.
        /// </summary>
        public void BulkInsertItems(string path)
        {
            // id'er på alle items i databasen, sorteret ASC - til binær søgning
            var all = dataAccess.AllItemIds(); 

            var table = new DataTable();

            //tilføjer DataColumns til table, indpakket i region for overskuelighed
            #region Tilføj DataColumns til DataTable

            var dc = new DataColumn("Id", Type.GetType("System.Int32"));
            table.Columns.Add(dc);

            dc = new DataColumn("GenericItemId", Type.GetType("System.Int32"));
            table.Columns.Add(dc);

            dc = new DataColumn("SizeId", Type.GetType("System.Int32"));
            table.Columns.Add(dc);

            dc = new DataColumn("ColorId", Type.GetType("System.Int32"));
            table.Columns.Add(dc);

            dc = new DataColumn("Quantity", Type.GetType("System.Int32"));
            table.Columns.Add(dc);

            dc = new DataColumn("Modified", Type.GetType("System.DateTime"));
            table.Columns.Add(dc);

            dc = new DataColumn("Available", Type.GetType("System.Int32"));
            dc.DefaultValue = 1;
            table.Columns.Add(dc);

            #endregion 

            using (var reader = XmlReader.Create(path)) 
            {
                reader.MoveToContent();
                while (reader.Read()) 
                {
                    if (reader.NodeType == XmlNodeType.Element) 
                    {
                        if (reader.Name == "Item") 
                        {
                            // generer XElement objekt for nuværende node, vha. LINQ
                            var element = XNode.ReadFrom(reader) as XElement; 
                            if (element == null) continue; 

                            // id for nuværende XElement
                            int id = Convert.ToInt32(element.Element("Id").Value); 

                            // Hvis XElementet's id ikke findes i vores collection med id's fra databasen
                            if (all.BinarySearch(id) < 0) 
                            {
                                DataRow row = table.NewRow();

                                // tilføjer XML elementets data til row
                                row["Id"] = id;
                                row["SizeId"] = Convert.ToInt32(element.Element("SizeId").Value);
                                row["ColorId"] = Convert.ToInt32(element.Element("ColorId").Value);
                                row["GenericItemId"] = Convert.ToInt32(element.Element("GenericItemId").Value);
                                row["Quantity"] = Convert.ToInt32(element.Element("Quantity").Value);
                                row["Modified"] = DateTime.Parse(element.Element("Modified").Value);

                                // tilføjer row til vores DataTable
                                table.Rows.Add(row); 
                            }
                        }
                    }
                }
            }

            // indsæt data table i databasen
            dataAccess.BulkInsertItems(table); 
        }

        /// <summary>
        /// Her opdaterer vi de items der har en ændring fra XML udtrækket
        /// </summary>
        void ModifyItems(string path)
        {
            // henter en dictionary med alle items id'er og DateTime for sidste ændring
            Dictionary<int, DateTime> dbDictionary = dataAccess.GetDateTimeFromItems();

            using (var reader = XmlReader.Create(path))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == "Item")
                        {
                            // generer XElement objekt for nuværende node, vha. LINQ
                            var el = XNode.ReadFrom(reader) as XElement;
                            if (el == null) continue;

                            // id for nuværende XElement
                            int currentId = Convert.ToInt32(el.Element("Id").Value);

                            // datetime for nuværende XElement
                            DateTime currentDateTime = DateTime.Parse(el.Element("Modified").Value);


                            DateTime dateTime;
                            // prøv at find DateTime i vores dictionary, vha. currentId
                            dbDictionary.TryGetValue(currentId, out dateTime);

                            // sammenlign databasens dato for sidste ændring, med XML elementets dato
                            // hvis XML elementet har et ældre timestamp, skal vi opdatere i databasen
                            if (DateTime.Compare(currentDateTime, dateTime) > 0)
                            {
                                int quantity = Convert.ToInt32(el.Element("Quantity").Value);
                                dataAccess.UpdateItem(currentId, quantity, currentDateTime);
                            }
                        }
                    }
                }
            }
        }

        #endregion
    }
}
